<?php
namespace App\Components\Admin;

use App\Components\Redis;
use Exception;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class XsrfToken
{
    // 加密方式
    private static $cipher = 'AES-256-CFB';

    public static $cookieName = 'XSRF-TOKEN';

    public static $csrfTokenName = 'csrf_token';

    private static $ivCachePrefix = 'COM:ADMIN:XSRFTOKEN:IV:';

    public static function generate(): array
    {
        if (!isset($_ENV['app_key']) || $_ENV['app_key'] == '') {
            throw new Exception('系统加密密钥尚未生成');
        }

        $plaintext = uniqid($_ENV['app_name'], true);
        $plaintext = md5($plaintext);
        $plaintext = strtoupper($plaintext);
        if (!in_array(self::$cipher, openssl_get_cipher_methods())) {
            throw new Exception('加密方式不支持');
        }

        $ivlen = openssl_cipher_iv_length(self::$cipher);
        $iv = openssl_random_pseudo_bytes($ivlen);

        Redis::setex(self::$ivCachePrefix . $plaintext, Redis::mediumTime(3), base64_encode($iv));

        $ciphertext = openssl_encrypt($plaintext, self::$cipher, $_ENV['app_key'], 0, $iv);
        return [$plaintext, $ciphertext];
    }

    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        if (!in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
            $response = $handler->handle($request);

            return $response;
        }

        if (!isset($_ENV['app_key']) || $_ENV['app_key'] == '') {
            throw new Exception('系统加密密钥尚未生成');
        }

        if (!isset($_COOKIE[self::$cookieName])) {
            throw new Exception('未发现COOKIE');
        }
        $body = $request->getParsedBody();
        if (!isset($body[self::$csrfTokenName])) {
            throw new Exception('在表单提交时，需使用csrf()|raw方法');
        }

        if (!in_array(self::$cipher, openssl_get_cipher_methods())) {
            throw new Exception('加密方式不支持');
        }

        $ivCacheKey = self::$ivCachePrefix . $_COOKIE[self::$cookieName];
        $iv = Redis::get($ivCacheKey);
        if ($iv === false || $iv === null) {
            throw new Exception('验签时间过久，请刷新重试');
        }
        $iv = base64_decode($iv);

        $ciphertext = $body[self::$csrfTokenName];
        unset($body);

        $plaintext = openssl_decrypt($ciphertext, self::$cipher, $_ENV['app_key'], 0, $iv);
        unset($ciphertext, $iv);

        if ($plaintext != $_COOKIE[self::$cookieName]) {
            throw new Exception('XSRF验证错误');
        }
        Redis::del($ivCacheKey);

        $response = $handler->handle($request);

        return $response;
    }
}
